 /*******************************************************************************
  * Copyright (c) 2004, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  * James D Miles (IBM Corp.) - bug 176250, Configurator needs to handle more platform urls
  *******************************************************************************/
 package org.eclipse.update.internal.configurator;

 import java.io.File ;
 import java.net.MalformedURLException ;
 import java.net.URL ;
 import java.util.Locale ;
 import java.util.MissingResourceException ;
 import java.util.ResourceBundle ;
 import java.util.StringTokenizer ;

 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.MultiStatus;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.osgi.framework.log.FrameworkLog;
 import org.eclipse.osgi.framework.log.FrameworkLogEntry;
 import org.eclipse.osgi.service.datalocation.Location;
 import org.eclipse.osgi.service.resolver.PlatformAdmin;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Filter;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
 import org.osgi.service.packageadmin.PackageAdmin;
 import org.osgi.util.tracker.ServiceTracker;

 public class Utils {
     private static final String PROP_ARCH = "osgi.arch"; //$NON-NLS-1$
 private static final String PROP_NL = "osgi.nl"; //$NON-NLS-1$
 private static final String PROP_OS = "osgi.os"; //$NON-NLS-1$
 private static final String PROP_WS = "osgi.ws"; //$NON-NLS-1$
 private static final String PI_OSGI = "org.eclipse.osgi"; //$NON-NLS-1$
 private static final String KEY_PREFIX = "%"; //$NON-NLS-1$
 private static final String KEY_DOUBLE_PREFIX = "%%"; //$NON-NLS-1$
 // os
 public static boolean isWindows = System.getProperty("os.name").startsWith("Win"); //$NON-NLS-1$ //$NON-NLS-2$
 static FrameworkLog log;
     private static ServiceTracker bundleTracker;
     private static ServiceTracker instanceLocation;
     private static ServiceTracker configurationLocation;

     public static void debug(String s) {
         if (ConfigurationActivator.DEBUG)
             System.out.println("PlatformConfig: " + s); //$NON-NLS-1$
 }
     
     /**
      * Creates a CoreException from some other exception.
      * The type of the CoreException is <code>IStatus.ERROR</code>
      * If the exception passed as a parameter is also a CoreException,
      * the new CoreException will contain all the status of the passed
      * CoreException.
      *
      * @see IStatus#ERROR
      * @param s exception string
      * @param e actual exception being reported
      * @return a CoreException
      * @since 2.0
      */
     public static CoreException newCoreException(String s, Throwable e) {

         // check the case of a multistatus
 IStatus status;
         if (e instanceof CoreException) {
             if (s == null)
                 s = ""; //$NON-NLS-1$
 status = new MultiStatus("org.eclipse.update.configurator", 0, s, e); //$NON-NLS-1$
 IStatus childrenStatus = ((CoreException) e).getStatus();
             ((MultiStatus) status).add(childrenStatus);
             ((MultiStatus) status).addAll(childrenStatus);
         } else {
             StringBuffer completeString = new StringBuffer (""); //$NON-NLS-1$
 if (s != null)
                 completeString.append(s);
             if (e != null) {
                 completeString.append(" ["); //$NON-NLS-1$
 String msg = e.getLocalizedMessage();
                 completeString.append(msg!=null?msg:e.toString());
                 completeString.append("]"); //$NON-NLS-1$
 }
             status = newStatus(completeString.toString(), e);
         }
         return new CoreException(status);
     }

     public static IStatus newStatus(String message, Throwable e) {
         return new Status(IStatus.ERROR, "org.eclipse.update.configurator", IStatus.OK, message, e); //$NON-NLS-1$
 }
     
     public static void log(String message) {
         log(newStatus(message, null));
     }
     
     public static void log(IStatus status) {
         if (log != null) {
             log.log(new FrameworkLogEntry(ConfigurationActivator.PI_CONFIGURATOR, status.getMessage(), 0, status.getException(), null));
         } else {
             System.out.println(status.getMessage());
             if (status.getException() != null)
                 status.getException().printStackTrace();
         }
     }
     
     /**
      * Close the services that we were listening to.
      */
     /*package*/ static synchronized void shutdown() {
         if (bundleTracker != null) {
             bundleTracker.close();
             bundleTracker = null;
         }
         if (instanceLocation != null) {
             instanceLocation.close();
             instanceLocation = null;
         }
         if (configurationLocation != null) {
             configurationLocation.close();
             configurationLocation = null;
         }
     }

     /**
      * Return a boolean value indicating whether or not we consider the
      * platform to be running.
      */
     public static boolean isRunning() {
         Bundle bundle = getBundle(PI_OSGI);
         return bundle == null ? false : bundle.getState() == Bundle.ACTIVE;
     }

     /**
      *
      */
     public static boolean isValidEnvironment(String os, String ws, String arch, String nl) {
         if (os!=null && !isMatching(os, getOS())) return false;
         if (ws!=null && !isMatching(ws, getWS())) return false;
         if (arch!=null && !isMatching(arch, getArch())) return false;
         if (nl!=null && !isMatchingLocale(nl, getNL())) return false;
         return true;
     }
     
     /**
      * Return the current operating system value.
      *
      * @see EnvironmentInfo#getOS()
      */
     public static String getOS() {
         return getContext().getProperty(PROP_OS);
     }

     /**
      * Return the current windowing system value.
      *
      * @see EnvironmentInfo#getWS()
      */
     public static String getWS() {
         return getContext().getProperty(PROP_WS);
     }

     /**
      * Return the current system architecture value.
      *
      * @see EnvironmentInfo#getOSArch()
      */
     public static String getArch() {
         return getContext().getProperty(PROP_ARCH);
     }

     /**
      * Return the current NL value.
      *
      * @see EnvironmentInfo#getNL()
      */
     public static String getNL() {
         return getContext().getProperty(PROP_NL);
     }
     
     /**
      * Returns a number that changes whenever the set of installed plug-ins
      * changes. This can be used for invalidating caches that are based on
      * the set of currently installed plug-ins. (e.g. extensions)
      *
      * @see PlatformAdmin#getState()
      * @see State#getTimeStamp()
      */
     public static long getStateStamp() {
         ServiceReference platformAdminReference = getContext().getServiceReference(PlatformAdmin.class.getName());
         if (platformAdminReference == null)
             return -1;
         PlatformAdmin admin = (PlatformAdmin) getContext().getService(platformAdminReference);
         return admin == null ? -1 : admin.getState(false).getTimeStamp();
     }

     /**
      * Return the resolved bundle with the specified symbolic name.
      *
      * @see PackageAdmin#getBundles(String, String)
      */
     public static synchronized Bundle getBundle(String symbolicName) {
         if (bundleTracker == null) {
             bundleTracker = new ServiceTracker(getContext(), PackageAdmin.class.getName(), null);
             bundleTracker.open();
         }
         PackageAdmin admin = (PackageAdmin) bundleTracker.getService();
         if (admin == null)
             return null;
         Bundle [] bundles = admin.getBundles(symbolicName, null);
         if (bundles == null)
             return null;
         //Return the first bundle that is not installed or uninstalled
 for (int i = 0; i < bundles.length; i++) {
             if ((bundles[i].getState() & (Bundle.INSTALLED | Bundle.UNINSTALLED)) == 0) {
                 return bundles[i];
             }
         }
         return null;
     }

     /*
      * Return the bundle context for this bundle.
      */
     private static BundleContext getContext() {
         return ConfigurationActivator.getBundleContext();
     }

     /**
      * Return the configuration location.
      *
      * @see Location
      */
     public static synchronized Location getConfigurationLocation() {
         if (configurationLocation == null) {
             Filter filter = null;
             try {
                 filter = getContext().createFilter(Location.CONFIGURATION_FILTER);
             } catch (InvalidSyntaxException e) {
                 // ignore this. It should never happen as we have tested the above format.
 }
             configurationLocation = new ServiceTracker(getContext(), filter, null);
             configurationLocation.open();
         }
         return (Location) configurationLocation.getService();
     }
     
     /**
      *
      */
     private static boolean isMatching(String candidateValues, String siteValues) {
         if (siteValues==null) return false;
         if ("*".equalsIgnoreCase(candidateValues)) return true; //$NON-NLS-1$
 siteValues = siteValues.toUpperCase();
         StringTokenizer stok = new StringTokenizer (candidateValues, ","); //$NON-NLS-1$
 while (stok.hasMoreTokens()) {
             String token = stok.nextToken().toUpperCase();
             if (siteValues.indexOf(token)!=-1) return true;
         }
         return false;
     }
     
     /**
      *
      */
     private static boolean isMatchingLocale(String candidateValues, String locale) {
         if (locale==null) return false;
         if ("*".equalsIgnoreCase(candidateValues)) return true; //$NON-NLS-1$

         locale = locale.toUpperCase();
         candidateValues = candidateValues.toUpperCase();
         StringTokenizer stok = new StringTokenizer (candidateValues, ","); //$NON-NLS-1$
 while (stok.hasMoreTokens()) {
             String candidate = stok.nextToken();
             if (locale.indexOf(candidate) == 0)
                 return true;
             if (candidate.indexOf(locale) == 0)
                 return true;
         }
         return false;
     }
     
     public static Locale getDefaultLocale() {
         String nl = getNL();
         // sanity test
 if (nl == null)
             return Locale.getDefault();
         
         // break the string into tokens to get the Locale object
 StringTokenizer locales = new StringTokenizer (nl,"_"); //$NON-NLS-1$
 if (locales.countTokens() == 1)
             return new Locale (locales.nextToken(), ""); //$NON-NLS-1$
 else if (locales.countTokens() == 2)
             return new Locale (locales.nextToken(), locales.nextToken());
         else if (locales.countTokens() == 3)
             return new Locale (locales.nextToken(), locales.nextToken(), locales.nextToken());
         else
             return Locale.getDefault();
     }
     
     
     /**
      * Returns a resource string corresponding to the given argument
      * value and bundle.
      * If the argument value specifies a resource key, the string
      * is looked up in the given resource bundle. If the argument does not
      * specify a valid key, the argument itself is returned as the
      * resource string. The key lookup is performed against the
      * specified resource bundle. If a resource string
      * corresponding to the key is not found in the resource bundle
      * the key value, or any default text following the key in the
      * argument value is returned as the resource string.
      * A key is identified as a string begining with the "%" character.
      * Note that the "%" character is stripped off prior to lookup
      * in the resource bundle.
      * <p>
      * For example, assume resource bundle plugin.properties contains
      * name = Project Name
      * <pre>
      * resolveNLString(b,"Hello World") returns "Hello World"</li>
      * resolveNLString(b,"%name") returns "Project Name"</li>
      * resolveNLString(b,"%name Hello World") returns "Project Name"</li>
      * resolveNLString(b,"%abcd Hello World") returns "Hello World"</li>
      * resolveNLString(b,"%abcd") returns "%abcd"</li>
      * resolveNLString(b,"%%name") returns "%name"</li>
      * </pre>
      * </p>
      *
      * @param resourceBundle resource bundle.
      * @param string translatable string from model
      * @return string, or <code>null</code>
      * @since 2.0
      */
     public static String getResourceString(ResourceBundle resourceBundle, String string) {

         if (string == null)
             return null;

         String s = string.trim();

         if (s.equals("")) //$NON-NLS-1$
 return string;

         if (!s.startsWith(KEY_PREFIX))
             return string;

         if (s.startsWith(KEY_DOUBLE_PREFIX))
             return s.substring(1);

         int ix = s.indexOf(" "); //$NON-NLS-1$
 String key = ix == -1 ? s : s.substring(0, ix);
         String dflt = ix == -1 ? s : s.substring(ix + 1);

         if (resourceBundle == null)
             return dflt;

         try {
             return resourceBundle.getString(key.substring(1));
         } catch (MissingResourceException e) {
             return dflt;
         }
     }

     public static boolean isAutomaticallyStartedBundle(String bundleURL) {
         if (bundleURL.indexOf("org.eclipse.osgi") != -1) //$NON-NLS-1$
 return true;
         
         String osgiBundles = ConfigurationActivator.getBundleContext().getProperty("osgi.bundles"); //$NON-NLS-1$
 StringTokenizer st = new StringTokenizer (osgiBundles, ","); //$NON-NLS-1$
 while (st.hasMoreTokens()) {
             String token = st.nextToken().trim();
             int index = token.indexOf('@');
             if (index != -1)
                 token = token.substring(0,index);
             if (token.startsWith("reference:file:")) { //$NON-NLS-1$
 File f = new File (token.substring(15));
                 if (bundleURL.indexOf(f.getName()) != -1)
                     return true;
             }
             if (bundleURL.indexOf(token) != -1)
                 return true;
         }
         return false;
     }

     /**
      * Returns an absolute URL by combining a base absolute URL and another URL relative to the first one.
      * If the relative URL protocol does not match the base URL protocol, or if the relative URL path is not relative,
      * return it as is.
      */
     public static URL makeAbsolute(URL base, URL relativeLocation) {
         if (!"file".equals(base.getProtocol())) //$NON-NLS-1$
 // we only deal with file: URLs
 return relativeLocation;
         if (relativeLocation.getProtocol() != null && !relativeLocation.getProtocol().equals(base.getProtocol()))
             // it is not relative, return as is (avoid creating garbage)
 return relativeLocation;
         IPath relativePath = new Path(relativeLocation.getPath());
         if (relativePath.isAbsolute())
             return relativeLocation;
         try {
             IPath absolutePath = new Path(base.getPath()).append(relativeLocation.getPath());
             // File.toURL() is the best way to create a file: URL
 return absolutePath.toFile().toURL();
         } catch (MalformedURLException e) {
             // cannot happen since we are building from two existing valid URLs
 Utils.log(e.getLocalizedMessage());
             return relativeLocation;
         }
     }

     /**
      * Returns a URL which is equivalent to the given URL relative to the
      * specified base URL. Works only for file: URLs
      */
     public static URL makeRelative(URL base, URL location) {
         if (base == null)
             return location;
         if (!"file".equals(base.getProtocol())) //$NON-NLS-1$
 return location;
         if (!base.getProtocol().equals(location.getProtocol()))
             return location;
         IPath locationPath = new Path(location.getPath());
         if (!locationPath.isAbsolute())
             return location;
         IPath relativePath = makeRelative(new Path(base.getPath()), locationPath);
         try {
             return new URL (base.getProtocol(), base.getHost(), base.getPort(), relativePath.toString());
         } catch (MalformedURLException e) {
             String message = e.getMessage();
             if (message == null)
                 message = ""; //$NON-NLS-1$
 Utils.log(Utils.newStatus(message, e));
         }
         return location;
     }

     /**
      * Returns a path which is equivalent to the given location relative to the
      * specified base path.
      */
     public static IPath makeRelative(IPath base, IPath location) {
         if (location.getDevice() != null && !location.getDevice().equalsIgnoreCase(base.getDevice()))
             return location;
         int baseCount = base.segmentCount();
         int count = base.matchingFirstSegments(location);
         String temp = ""; //$NON-NLS-1$
 for (int j = 0; j < baseCount - count; j++)
             temp += "../"; //$NON-NLS-1$
 return new Path(temp).append(location.removeFirstSegments(count));
     }

     /**
      * Returns a string URL which is equivalent to the given absolute location
      * made relative to the specified base path.
      */
     public static String makeRelative(URL base, String absolute) {
         try {
             return makeRelative(base, new URL (absolute)).toExternalForm();
         } catch (MalformedURLException e) {
             // returns the original string if is invalid
 return absolute;
         }
     }

     /**
      * Ensures file: URLs on Windows have the right form (i.e. '/' as segment separator, drive letter in lower case, etc)
      */
     public static String canonicalizeURL(String url) {
         if (!(isWindows && url.startsWith("file:"))) //$NON-NLS-1$
 return url;
         try {
             String path = new URL (url).getPath();
             // normalize to not have leading / so we can check the form
 File file = new File (path);
             path = file.toString().replace('\\', '/');
             if (Character.isUpperCase(path.charAt(0))) {
                 char[] chars = path.toCharArray();
                 chars[0] = Character.toLowerCase(chars[0]);
                 path = new String (chars);
                 return new File (path).toURL().toExternalForm();
             }
         } catch (MalformedURLException e) {
             // default to original url
 }
         return url;
     }

     /**
      * Return the install location.
      *
      * @see Location
      */
     public static synchronized URL getInstallURL() {
         if (instanceLocation == null) {
             Filter filter = null;
             try {
                 filter = getContext().createFilter(Location.INSTALL_FILTER);
             } catch (InvalidSyntaxException e) {
                 // ignore this. It should never happen as we have tested the
 // above format.
 }
             instanceLocation = new ServiceTracker(getContext(), filter, null);
             instanceLocation.open();
         }

         Location location = (Location) instanceLocation.getService();

         // it is pretty much impossible for the install location to be null. If it is, the
 // system is in a bad way so throw and exception and get the heck outta here.
 if (location == null)
             throw new IllegalStateException ("The installation location must not be null"); //$NON-NLS-1$

         return location.getURL();
     }

 }

